﻿/// <summary>
/// Developer: CHOOI KAH WAI
/// Last Modified: 16/11/2009
/// </summary>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Net.Sockets;
using System.IO;
using System.ComponentModel;

namespace ARSS
{
    /// <summary>
    /// Holds network client information
    /// </summary>
    public class ClientData : INotifyPropertyChanged
    {
        #region Variables

        /// <summary>
        /// Client name
        /// </summary>
        protected string clientName;

        /// <summary>
        /// Data receive buffer
        /// </summary>
        protected MemoryStream receiveBuffer;

        /// <summary>
        /// Total data size
        /// -1 indicates this is new packet
        /// </summary>
        protected int dataSizeToReceive = -1;

        #endregion

        #region Delegates / Events

        /// <summary>
        /// Data receive
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="data"></param>
        public delegate void DataReceivedHandler(ClientData sender, byte[] data);
        public event DataReceivedHandler DataReceived = null;

        /// <summary>
        /// Disconnected
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="data"></param>
        public delegate void ClientDisconnectedHandler(ClientData sender);
        public event ClientDisconnectedHandler ClientDisconnected = null; 

        #endregion

        #region Properties

        /// <summary>
        /// Client name
        /// </summary>
        public string Name 
        {
            get
            {
                return this.clientName;
            }
            set
            {
                this.clientName = value;
                OnPropertyChanged("Name");
            }
        }

        /// <summary>
        /// Scene objects currently held by this client
        /// </summary>
        public ArrayList LockedObjects { get; set; }

        /// <summary>
        /// At server side, this is the connected client socket
        /// At client side, this is the socket connected to server
        /// </summary>
        public Socket TCPSocket { get; set; }

        #endregion

        #region Functions

        /// <summary>
        /// Constructor
        /// </summary>
        public ClientData()
        {
            Name = "";
            LockedObjects = new ArrayList();
            TCPSocket = null;
            this.receiveBuffer = new MemoryStream();
        }

        /// <summary>
        /// Start receiving data
        /// </summary>
        public void ReceiveData()
        {
            // Maximum packet size set to 128 bytes
            byte[] buffer = new byte[128];

            SocketAsyncEventArgs evt = new SocketAsyncEventArgs();
            evt.UserToken = TCPSocket;
            evt.SetBuffer(buffer, 0, 128);
            evt.Completed += new EventHandler<SocketAsyncEventArgs>(OnDataReceived);
            if (TCPSocket.ReceiveAsync(evt) == false)
            {
                ProcessData(evt);
            }
          
        }

        /// <summary>
        /// Process the buffer
        /// </summary>
        /// <param name="e"></param>
        public void ProcessData(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                if (e.BytesTransferred == 0)
                {
                    Disconnect();
                    return;
                }

                int offset = 0;
                if (this.dataSizeToReceive == -1)
                {
                    //Retrieve the total data size
                    byte[] dataSize = new byte[4];
                    Buffer.BlockCopy(e.Buffer, 0, dataSize, 0, 4);
                    this.dataSizeToReceive = BitConverter.ToInt32(dataSize, 0);
                    offset = 4;
                }

                // Validation
                if (e.BytesTransferred - offset < 0)
                {
                    Disconnect();
                    return;
                }

                this.receiveBuffer.Write(e.Buffer, offset, e.BytesTransferred - offset);

                // Full data received
                if (this.receiveBuffer.Length >= this.dataSizeToReceive)
                {
                    if (DataReceived != null)
                        DataReceived(this, this.receiveBuffer.ToArray());

                    // Reset the data size
                    this.dataSizeToReceive = -1;
                    // Reset receive buffer
                    this.receiveBuffer = new MemoryStream();

                }


                // Keep receiving data
                ReceiveData();
            }
            else
            {
                Disconnect();
            }
        }

        /// <summary>
        /// Data received on socket
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void OnDataReceived(object sender, SocketAsyncEventArgs e)
        {
            ProcessData(e);
        }

        /// <summary>
        /// Send data to socket
        /// </summary>
        /// <param name="dataBuffer"></param>
        public void SendData(byte[] dataBuffer, int length)
        {
            if (!TCPSocket.Connected) return;

            byte[] sendBuffer = new byte[4 + length];
            // Pad the data size to front most of the send buffer
            Buffer.BlockCopy(BitConverter.GetBytes(length), 0, sendBuffer, 0, 4);
            // Now pad the "real data behind
            Buffer.BlockCopy(dataBuffer, 0, sendBuffer, 4, length);

            SocketAsyncEventArgs evt = new SocketAsyncEventArgs();
            evt.UserToken = TCPSocket;
            evt.SetBuffer(sendBuffer, 0, sendBuffer.Length);
            evt.Completed += new EventHandler<SocketAsyncEventArgs>(DataSent);
            TCPSocket.SendAsync(evt);
        }

        /// <summary>
        /// Data sent out asynchronous callback
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void DataSent(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError != SocketError.Success)
            {
                Disconnect();
            }
        }

        /// <summary>
        /// Disconnect client
        /// </summary>
        public void Disconnect()
        {
            if (TCPSocket.Connected)
            {
                TCPSocket.Shutdown(SocketShutdown.Both);
            }
            TCPSocket.Close();
            if (ClientDisconnected != null)
                ClientDisconnected(this);
        }

        protected void RaiseDataReceivedEvent(byte[] data)
        {
            if (DataReceived != null)
                DataReceived(this, data);
        }

        #endregion

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Property changed event
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Notify property changed
        /// </summary>
        /// <param name="propertyName"></param>
        public void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}
